/* Game.java
 * 
 * Alfred Olschnoegger
 * 22.05.2008
 */

package domain.game;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import domain.GameObject;
import domain.Hunter;
import domain.Rabbit;
import domain.location.GameArea;
import domain.location.Position;
import domain.objectRules.*;
import exceptions.GameEndedException;
import exceptions.LogWriter;

/**
 * This class represents the game itselfe and is the central data keeping class
 * of the game.
 * 
 * @date 22.05.2008
 * @author Alfred Olschnoegger
 */

public class Game extends Thread {
	private static final int s_TimeSliceLenght = 30; // timesclice in sec
	private static final double s_InitRabbitsPerField = 0.15;
	private static final double s_MaxRabbitsPerField = 0.30;
	private static final double s_MinRabbitsPerField = 0.05;

	private HashMap<String, Hunter> m_Hunters = new HashMap<String, Hunter>();
	private HashMap<Integer, GameObject> m_Objects = new HashMap<Integer, GameObject>();
	private HashMap<Position, ArrayList<GameObject>> m_ObjectPositions = new HashMap<Position, ArrayList<GameObject>>();
	private HashMap<Integer, GameObject> m_InTimeSliceObjects = new HashMap<Integer, GameObject>();

	private int m_RemainingTimeslices;
	private int m_GameID;
	private String m_Name;
	private GameArea m_Area;

	/**
	 * Constructor of this class (only available from the factory class)
	 * 
	 * @param name
	 *            Name of the game
	 * @param gameID
	 *            ID of the game
	 * @param runningtime
	 *            Game Duration in minutes, -1 means infinitive
	 * @param area
	 *            Game Area
	 */
	protected Game(String name, int gameID, int runningtime, GameArea area) {
		m_GameID = gameID;
		m_Area = area;
		m_Name = name;
		if ((runningtime == -1) || (runningtime == 0)) {
			m_RemainingTimeslices = -1;
		} else {
			m_RemainingTimeslices = runningtime * 60 / s_TimeSliceLenght;
		}

	}

	/**
	 * Adds a hunter to the game
	 * 
	 * @param hunter
	 *            the hunter
	 * @return true if the hunter was successfully added
	 */
	public boolean addHunter(Hunter hunter) throws GameEndedException {
		if (m_RemainingTimeslices == 0)
			throw new GameEndedException();

		if (m_Hunters.containsKey(hunter.getName())) {
			return false;
		}

		m_Hunters.put(hunter.getName(), hunter);
		return true;
	}

	/**
	 * initializes the game
	 */
	private void init() {
		Rabbit rabbit;
		ObjectRule r1 = new AgingRule(70);
		ObjectRule r2 = new ReproductionRule();
		ObjectRule r3 = new MovementRule();

		for (int i = 0; i < m_Area.getFieldCount() * s_InitRabbitsPerField; i++) {
			rabbit = new Rabbit(m_Area.getRandPosition());
			rabbit.addRule(r1);
			rabbit.addRule(r2);
			rabbit.addRule(r3);
			add(rabbit);
		}
		proceedTSChanges();
	}

	public void proceedTSChanges() {
		m_Objects = m_InTimeSliceObjects;
		m_InTimeSliceObjects = new HashMap<Integer, GameObject>();
		m_ObjectPositions = new HashMap<Position, ArrayList<GameObject>>();

		for (Integer key : m_Objects.keySet()) {
			GameObject o = m_Objects.get(key);
			if (!m_ObjectPositions.containsKey(o.getPos())) {
				m_ObjectPositions.put(o.getPos().clone(), new ArrayList<GameObject>());
			}
			m_ObjectPositions.get(o.getPos()).add(o);
		}

	}

	/**
	 * this method is invoked when the game is started
	 */
	public void run() {
		init();
		while (m_RemainingTimeslices != 0) {
			GameObject tmp;
			for (Integer i : m_Objects.keySet()) {
				tmp = m_Objects.get(i);
				if (tmp.isActive()) {
					tmp.applyRules(this);
				}

				if (tmp.isActive()) {
					add(tmp);
				}
			}
			
			
			if(m_InTimeSliceObjects.size() >= (s_MinRabbitsPerField * m_Area.getFieldCount())) {
				proceedTSChanges();
			} else {
				init();
			}
			
			if (m_RemainingTimeslices > 0)
				m_RemainingTimeslices--;

			try {
				sleep(s_TimeSliceLenght * 1000);
			} catch (InterruptedException e) {
				new LogWriter().write(e);
				e.printStackTrace();
			}

		}
		GameManager.getInstance().gameEnded(m_GameID);
	}

	/**
	 * 
	 * @return the game id
	 */
	public int getGameID() {
		return m_GameID;
	}

	/**
	 * adds a new object to the game
	 * 
	 * @param object
	 *            the object which should be added
	 * @return true if the object was added successfully
	 */
	public synchronized boolean add(GameObject object) {
		if (!m_InTimeSliceObjects.containsKey(object.getID())) {
			m_InTimeSliceObjects.put(object.getID(), object);
			return true;
		}
		return false;
	}

	/**
	 * @return the remaining time slices
	 */
	public int getRemainingTimeslice() {
		return m_RemainingTimeslices;
	}

	/**
	 * 
	 * @return amount of the hunters
	 */
	public int getHunterCount() {
		return m_Hunters.size();
	}

	/**
	 * returns all hunters with a valid position
	 */
	public List<Hunter> getValidHunterNames() {
		List<Hunter> hunters = new LinkedList<Hunter>();
		for(Hunter h : m_Hunters.values())
			if(h.isValid()) 
				hunters.add(h);
		
		
		return hunters;
	}
	
	
	/**
	 * returns all hunter values
	 */
	public List<Hunter> getHunterNames() {
		List<Hunter> list = new LinkedList<Hunter>();
		list.addAll(m_Hunters.values());
		
		return list;
	}

	/**
	 * 
	 * @return returns the game name
	 */
	public String getGameName() {
		return m_Name;
	}

	/**
	 * 
	 * @param name
	 *            sets the game name
	 */
	public void setGameName(String name) {
		m_Name = name;
	}

	/**
	 * 
	 * @param hunterName
	 *            name of the searched hunter
	 * @return the found hunter
	 */
	public Hunter getHunter(String hunterName) {
		return m_Hunters.get(hunterName);
	}

	/**
	 */
	public synchronized void end() {
		m_RemainingTimeslices = 0;
	}

	/**
	 * @param hunterName
	 *            name of the searched hunter
	 * @return true if the hunter is already in the container
	 */
	public boolean containsHunter(String hunterName) {
		return m_Hunters.containsKey(hunterName);
	}

	public final GameArea getArea() {
		return m_Area;
	}

	public synchronized boolean use(int object, Hunter hunt) {
		if (m_Objects.containsKey(object) && m_Objects.get(object).isActive()) {
			m_Objects.get(object).use(hunt, this);

			return true;
		}
		return false;
	}

	public Collection<GameObject> getRange(Position pos, int range) {
		Collection<GameObject> objs = new LinkedList<GameObject>();
		if (range == -1) {
			for (Integer key : m_Objects.keySet()) {
				objs.add(m_Objects.get(key));
			}
		} else {

			m_Area.moveUnchecked(pos, -range, -range);
			for (Position p : m_ObjectPositions.keySet())
				System.out.println(p.getX() + "," + p.getY());
			System.out.println("-----------------------------------------");
			for (int i = 0; i < range * 2 + 1; i++) {
				for (int k = 0; k < range * 2 + 1; k++) {
					if (m_ObjectPositions.containsKey(pos)) {
						objs.addAll(m_ObjectPositions.get(pos));
						System.out.println("add");
					}
					System.out.println(pos.getX() + "," + pos.getY());
					m_Area.moveUnchecked(pos, 0, 1);
				}
				m_Area.moveUnchecked(pos, 1, -(range * 2 + 1));
			}
		}
		return objs;
	}

	public Collection<GameObject> getObjectsAt(Position pos) {
		Collection<GameObject> objs = new LinkedList<GameObject>();

		if (m_ObjectPositions.containsKey(pos))
			objs.addAll(m_ObjectPositions.get(pos));

		return objs;
	}

	public boolean MaxRabbitsReached() {
		if (m_Objects.size() >= (m_Area.getFieldCount() * s_MaxRabbitsPerField))
			return true;
		return false;
	}

	public Collection<GameObject> getGameObjects() {
		Collection<GameObject> objs = new LinkedList<GameObject>();

		for (Integer key : m_Objects.keySet())
			objs.add(m_Objects.get(key));

		return objs;
	}
	
	public boolean isRunning() {
		if(m_RemainingTimeslices == 0)
			return false;
		return true;
	}
}

// end of Game.java
